package preProcessTrace;

import utils.Utils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.*;

import static dslProgramGen.util.DslProgramGenUtil.renameClassNameReplaceSlashWithUnderlineAndRenameObjectClassName;
import static preProcessTrace.PreProcessTraces.isNeededDslTrace;
import static utils.Utils.*;


public class FilterTraces {
    /**
     * 源程序的main函数所在的主类名
     */
    public static String mainClassName;

    /**
     * 判断一个threadName是不是需要的（JVM内部会启动一些thread，这些thread不是我们需要的），如果是返回true，不是返回false
     *
     * @param threadName 待判断的线程名称
     * @return 如果是需要的线程名，返回true
     */
    public static boolean isNeededThreadName(String threadName) {
        // 如果threadName == null，我们认为也是不需要的线程名
        if (threadName == null) {
            return false;
        }
        /**不需要的线程名列表
         * null
         * Reference Handler
         * Unknown thread
         * Common-Cleaner
         * DestroyJavaVM
         * Signal Dispatcher
         * Finalizer
         * <no-name - thread is attaching>
         */
        String[] uselessThreadNames = {"null", "Reference Handler", "Unknown thread", "Common-Cleaner", "DestroyJavaVM", "Signal Dispatcher",
                "Finalizer", "<no-name - thread is attaching>", "Monitor Ctrl-Break", "Logging-Cleaner"};
        for (String uselessThreadName : uselessThreadNames) {
            if (uselessThreadName.equals(threadName)) {
                return false;
            }
        }
        return true;
    }


    /**
     * 初始的trace序列包含GC日志和我们定义的DSL语句，该函数主要有以下功能
     * 1. 清洗trace，去除GC日志部分
     * 2. 删除我们不需要的自定义trace语句，具体详见isNeededDslTrace函数
     * 3. 保留我们想要线程的trace，有些线程是JVM内部的，这些线程不是我们想要的，不想要的线程名参见isNeededThreadName函数
     * 4. 将大的trace按线程名分为小的trace，特别对于main线程而言，保留应用程序main函数的trace内容
     *
     * @param srcFilePath   源trace文件路径
     * @param dstFolderPath 处理后的trace文件夹的路径
     * @throws FileNotFoundException
     */
    public static void filterTraceRecords(String srcFilePath, String dstFolderPath) throws FileNotFoundException {
        // 如果dstFolderPath存在，则删除
        deleteExsitDstFolder(dstFolderPath);

        // 输出Add Set root和Add Class root和NewS以及 main线程main函数开始前的New Traces 以及我们认为是JVM内部线程的New操作的文件路径
        String beforeMainMethodNewObjTracesPath = dstFolderPath + "beforeMainNewObjTraces.txt";
        // 输出 main线程main函数开始前的Put和PutA Traces 以及我们认为是JVM内部线程 Put和PutA 的文件路径
        String beforeMainMethodPutPutATracesPath = dstFolderPath + "beforeMainPutPutATraces.txt";

        // 用一个set保留应用程序运行的线程名称
        Set<String> appThreadNamesSet = new HashSet<>();
        Scanner scanner = new Scanner(new FileReader(srcFilePath));
        boolean isMain = false; // main线程使用，表示当前的trace是不是在应用程序main函数的范围内
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            // 处理主要是区分GC trace还是我们自定义DSL trace
            if (Utils.isInputContainsPattern(line, "\\[gc,heap\\s+\\] (New|NewM|NewL|PutA|Put|Add|Del|Rep|Return|Get|GetA|NewS)")) {
                // 将Add Set root和Add Class root和NewS写入到beforeMainNewObjTraces.txt文件里面去
                if (Utils.isInputContainsPattern(line, "\\[gc,heap\\s+\\] (Add Set root|Add Class root|NewS)")) {
                    // 将这行trace输出到文件中
                    writeLineToDstFile(line, beforeMainMethodNewObjTracesPath);
                    continue;
                }
                // 获得这一行trace所在线程名称
                String threadName = RegularExpressionProcess(line, "T:(.*?)(,|$)");

                // 如果是需要的线程名
                if (isNeededThreadName(threadName)) {
                    // 将main线程main函数开始前的New写入到beforeMainNewObjTraces.txt文件里面去
                    if ("main".equals(threadName) && !isMain && Utils.isInputContainsPattern(line, "\\[gc,heap\\s+\\] (New)")) {
                        // 将这行trace输出到文件中
                        writeLineToDstFile(line, beforeMainMethodNewObjTracesPath);
                        continue;
                    }
                    // 将main线程main函数前面的Put和PutA traces输出到文件中
                    if ("main".equals(threadName) && !isMain && Utils.isInputContainsPattern(line, "\\[gc,heap\\s+\\] (PutA|Put)")) {
                        writeLineToDstFile(line, beforeMainMethodPutPutATracesPath);
                        continue;
                    }

                    appThreadNamesSet.add(threadName);
                    if ("main".equals(threadName)) {
                        String dstFilePath = dstFolderPath + threadName + ".txt"; // 这行trace待写入的文件路径
                        // 如果是main线程，则要截取应用程序的main函数
                        // 判断line是不是main函数开始
                        // main函数开始trace例子 [26.323s][info][gc,heap     ] Add PR H:0x00000000266713d4, C:[Ljava/lang/String;, T:main, MIS:true, M:Test.main ([Ljava/lang/String;)V
                        if (Utils.isInputContainsPattern(line, "Add PR H:.*?, C:\\[Ljava/lang/String;, T:main, MIS:true, M:" + mainClassName + "\\.main \\(\\[Ljava/lang/String;\\)V")) {
                            // 如果这行内容是main函数开始的trace
                            isMain = true;
                        }
                        if (isMain) {
                            // 如果是main函数范围的trace，才进行处理
//                                writeDslTraceToFile(line, dstFilePath);
                            writeLineToDstFile(line, dstFilePath);
                        }
                        // 判断line是不是main函数的结束
                        // main函数结束trace例子 [51.676s][info][gc,heap     ] Return H:NULL, C:NULL, T:main, MIS:true, M:dacapo/TestHarness.main ([Ljava/lang/String;)V
                        if (Utils.isInputContainsPattern(line, "Return H:.*?, C:.*?, T:main, MIS:true, M:" + mainClassName + "\\.main \\(\\[Ljava/lang/String;\\)V")) {
                            isMain = false;
                        }
                    } else {
                        String dstFilePath = dstFolderPath + threadName + "_temp.txt"; // 这行trace待写入的文件路径
                        // 其他线程直接输出线程的trace到对应文件
//                            writeDslTraceToFile(line, dstFilePath);
                        writeLineToDstFile(line, dstFilePath);
                    }
                } else {
                    // 我们认为这些线程是JVM内部线程，只保留New 和 Put PutA traces
                    // 将New trace写入到beforeMainNewObjTraces.txt文件里面去
                    if (Utils.isInputContainsPattern(line, "\\[gc,heap\\s+\\] (New)")) {
                        // 将这行trace输出到文件中
                        writeLineToDstFile(line, beforeMainMethodNewObjTracesPath);
                        continue;
                    }
                    // 将Put和PutA traces输出到文件中
                    if (Utils.isInputContainsPattern(line, "\\[gc,heap\\s+\\] (PutA|Put)")) {
                        writeLineToDstFile(line, beforeMainMethodPutPutATracesPath);
                    }
                }
//                }

            }

        }
//        // 输出应用程序所有的线程名
//        for (String appThreadName : appThreadNamesSet) {
//            System.out.println(appThreadName);
//        }
        // 保存每个非main thread的信息，按顺序为 传入的Thread对象id, 线程名称, run方法MethodName, 线程类名
        // 例如 Add PR H:0x0000000076462bc3, C:MyThread, T:Thread-0, M:MyThread.run ()V
        List<List<String>> threadInfoLists = new ArrayList<>();

        // 对每个非main线程的trace再进行处理，去除掉run方法之外的Traces，将处理后的traces保存在 threadName.txt 文件中
        // 并在处理的过程中记录Thread的信息保存到threadInfoLists中
        for (String threadName : appThreadNamesSet) {
            // 可能有为null的线程 null | null | null | null |
            if (!"main".equals(threadName) && threadName != null && !"null".equals(threadName)) {
                filterOneThreadTraces(dstFolderPath + threadName + "_temp.txt",
                        dstFolderPath + threadName + ".txt", threadName, threadInfoLists);
            }
        }
        // 将每个非main线程的线程信息输出到文件 ThreadsInfo.txt中
        String threadInfoFilePath = dstFolderPath + "ThreadsInfo.txt";
        writeThreadsInfoToDslFile(threadInfoLists, threadInfoFilePath);

        // 把main函数前面的新建对象和PutPutA traces两个文件合并在一起
        // 合并beforeMainNewObjTraces.txt和beforeMainPutPutATraces.txt两个文件
        mergeFiles(beforeMainMethodPutPutATracesPath, beforeMainMethodNewObjTracesPath);
        // 重命名beforeMainMethodNewObjTracesPath文件名
        File oldFile = new File(beforeMainMethodNewObjTracesPath);
        File newFile = new File(dstFolderPath + "beforeMainUsefulTraces.txt");
        oldFile.renameTo(newFile);
        // 删除文件beforeMainMethodPutPutATracesPath
        File file = new File(beforeMainMethodPutPutATracesPath);
        if (file.exists()) {
            file.delete();
        }
    }

    /**
     * 将非main线程的线程信息输出到目标文件中
     *
     * @param threadInfoLists    非main线程的线程信息，包括 传入的Thread对象id, 线程名称, run方法MethodName, 线程类名
     * @param threadInfoFilePath 目标文件路径
     */
    public static void writeThreadsInfoToDslFile(List<List<String>> threadInfoLists, String threadInfoFilePath) {
        for (List<String> threadInfoListTemp : threadInfoLists) {
            // 将threadInfoListTemp里的string拼接成一行内容
            StringBuilder threadInfoStringBuilder = new StringBuilder();
            for (String infoStr : threadInfoListTemp) {
                threadInfoStringBuilder.append(infoStr + " | ");
            }
            writeLineToDstFile(threadInfoStringBuilder.toString(), threadInfoFilePath);
        }
    }

    /**
     * 由filterTraceRecords函数处理raw traces后生成的某个线程（非main线程）的traces里面除了run方法，在run方法结束的位置后还有其他内容
     * 1. 该方法想要去掉run方法结束后的其他内容
     * 2. 保存每个非main线程的信息，按顺序为 传入的Thread对象id, 线程名称, run方法MethodName, 线程类名
     *
     * @param srcFilePath     线程的源trace文件路径，例如Thread-0_temp.txt
     * @param dstFilePath     过滤了run方法结束位置后的内容，得到的traces所在的文件路径，例如Thread-0.txt
     * @param threadName      处理的线程名，例如Thread-0
     * @param threadInfoLists 保存每个非main thread的信息，按顺序为 传入的Thread对象id, 线程名称, run方法MethodName, 线程类名
     */
    public static void filterOneThreadTraces(String srcFilePath, String dstFilePath, String threadName, List<List<String>> threadInfoLists) throws FileNotFoundException {
        // 判断dstFilePath文件是否存在，如果存在，则删除
        File file = new File(dstFilePath);
        if (file.exists()) {
            file.delete();
        }

        Scanner scanner = new Scanner(new FileReader(srcFilePath));
        boolean isRunMethod = false; // 表示当前的trace是不是在run函数的范围内
        boolean isFirstLine = true; // 当前处理的trace是不是第一行
        String threadClassName = null; // 获得当前线程的类名
        String runMethodName = null; // 获得当前线程运行的run方法的名称
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            // 处理主要是区分GC trace还是我们自定义DSL trace
            if (Utils.isInputContainsPattern(line, "\\[gc,heap\\s+\\] (New|NewM|NewL|PutA|Put|Add|Del|Rep|Return|Get|GetA)")) {
                // 是我们自定义的trace，判断是不是我们需要的trace
                if (isNeededDslTrace(line)) {
                    if (isFirstLine) {
                        // 如果是第一行，获得线程的类名和run方法的名称
                        threadClassName = RegularExpressionProcess(line, "C:(.*?),");
//                        threadClassName = renameClassNameReplaceSlashWithUnderlineAndRenameObjectClassName(threadClassName);
                        runMethodName = RegularExpressionProcess(line, "M:(.*?)$");
                        // 保存该线程的信息
                        List<String> threadInfoList = new ArrayList<>();
                        // 获得传入的Thread对象id
                        String threadObjId = RegularExpressionProcess(line, "H:(.*?),");
                        // 将thread相关信息按顺序加入到threadInfoList中（顺序为传入的Thread对象id, 线程名称, run方法MethodName, 线程类名）
                        threadInfoList.add(threadObjId);
                        threadInfoList.add(threadName);
                        threadInfoList.add(runMethodName);
                        threadInfoList.add(renameClassNameReplaceSlashWithUnderlineAndRenameObjectClassName(threadClassName));
                        // 将当前线程信息保存到threadInfoLists
                        threadInfoLists.add(threadInfoList);
                        isFirstLine = false;
                    }

                    // 要截取线程的run函数
                    // 判断line是不是run函数开始
                    // run函数开始的例子 [26.908s][info][gc,heap     ] Add PR H:0x0000000065988f4f, C:MyThread, T:Thread-0, MIS:false, M:MyThread.run ()V
                    if (Utils.isInputContainsPattern(line, "Add PR H:.*?, C:.*?, T:" + threadName + ", MIS:false, M:.*?\\.run \\(\\)V")) {
                        // 如果这行内容是run函数开始的trace
                        isRunMethod = true;
                    }
                    if (isRunMethod) {
                        // 如果是run函数范围的trace，才进行处理
                        writeLineToDstFile(line, dstFilePath);
                    }
                    // 判断line是不是run函数的结束
                    // [51.676s][info][gc,heap     ] Return H:NULL, C:NULL, T:main, MIS:true, M:dacapo/TestHarness.main ([Ljava/lang/String;)V
                    if (Utils.isInputContainsPattern(line, "Return H:.*?, C:.*?, T:" + threadName + ", MIS:false, M:" + threadClassName + "\\.run \\(\\)V")) {
                        isRunMethod = false;
                    }
                }

            }

        }
        scanner.close();
        // 删除srcFilePath
        File srcFile = new File(srcFilePath);
        if (srcFile.exists()) {
            srcFile.delete();
        }
    }

    public static void main(String[] args) throws FileNotFoundException {
        // 注意: 运行过滤程序前需要配置一些参数
        // 参数1 运行的Java程序main函数所在的主类名
        mainClassName = "Benchmark";

        // 下面的num变量主要用于辅助生成srcFilePath和dstFolderPath，可任意修改
        int num = 1;
        // 源trace文件所在的路径
        String srcFilePath = "src/test/resources/dslProgramGenForComplexMop/rawTraces/traces_" + num + ".txt";
        // trace清洗后的内容所在的文件夹路径，注意如果路径存在，会删除该路径里的所有文件和文件夹
        String dstFolderPath = "src/test/resources/dslProgramGenForComplexMop/filteredTraces/" + num + "/";

        long startTime = System.currentTimeMillis();
        filterTraceRecords(srcFilePath, dstFolderPath);
        long endTime = System.currentTimeMillis();
        long executionTime = endTime - startTime;
        System.out.println("用时: " + executionTime / 1000.0 / 3600.0 + "小时");
    }
}
